
"""
"""

from fal.event import EventManager

#########################
## WEAPON CARRIERS

class Attacker(object):
    """
    Something that can attack, and can be attacked.
    """
    hasFired = False
    inFirePhase = False
    
    def __init__(self, **kwargs):
        super(Attacker, self).__init__(**kwargs)
        self.hasFired = False
        self.inFirePhase = False
        
    def hasWeapon(self, weapon):
        """
        Returns True if this attacker has that weapon. Allows some default code in the base class.
        """
        raise NotImplementedError
    
    def getObscured(self, target):
        #TODO: Get obscrured info
        return False
    
    def getDistance(self, target):
        #TODO: Get distance info. Override in derived?
        return 1
    
    def attack(self, target, weapon):
        """Attack the given target with said weapon."""
        if not self.hasWeapon(weapon):
            raise ValueError, "Attacker %r doesn't have weapon %r" % \
                (self, weapon)
        weapon.fire(target, self.getObscured(target), self.getDistance(target))
    

class Attackable(object):
    inFirePhase = False
    toHitRoll = property(self.__getToHit)
    __baseToHit = 0
    
    def __getToHit(self):
        if hasattr(self, 'applySkills'):
            return self.applySkills('combat.defend.toHit', self.__baseToHit)
    
    def parry(self, weapon, damage):
        """
        Used to figure out whether a character parries some weapons' 
        damage. Weapon calls this after figuring out how much damage it
        would deal, if weapon can be parried (pistols, rifles, and cannons
        can't be parried).
        """
        raise NotImplementedError
    
    def takeDamage(self, damage, weapon, dieRoll=None):
        """
        Tells the target how much damage a weapon thinks the target took,
        in addition to the die roll used to determine that.
        """
        raise NotImplementedError
    
    EventManager.registerEvent('combat.death')
    def notifyDeath(self):
        """Notify the world that this sucker is dead."""
        #TODO: Factor in the network traffic
        EventManager.triggerEvent('combat.death', self)
    
    def repair(self, modifier):
        """Repair health by modifier, given on a game-wide scale."""
        raise NotImplementedError

#########################
## WEAPON TYPES

class Weapon(object):
    """
    Defines the basic interface for a weapon. Animals can 
    have weapons, too; their mouth.
    """
    
    logger = logging.getLogger('engine.Weapon')
    owner = None
    
    toHitDice = None
    toHitMin = 2
    criticalHitMin = 3
    criticalMissMax = 1
    criticalMissDamage = 1
    normalHitDamage = 2
    criticalHitDamage = 4
    
    dropable = True
    
    def __init__(self, holder, **kwargs):
        super(Weapon, self).__init__(**kwargs)
        self.owner = holder
    
    def fire(self, target, obscured, distanceToTarget):
        """
        Used to actually fire a weapon. Return value is not significant.
        """
        raise NotImplementedError
    
    def getDamage(self, obscured, distanceToTarget):
        """
        Returns two values:
        -A boolean indicating that it hit the target (vs. its owner, default True)
        -A number indicating damage (0 if missed)
        """
        raise NotImplementedError
    
    def parry(self, attacker, parryBonus=0):
        """
        Used to block some weaponry, mostly cutlasses. A certain amount
        of associated logic will need to be in the owner. Returns whether
        or not the weapon parries this turn.
        """
        raise NotImplementedError

class RangedWeapon(Weapon):
    """
    For pistols, rifles, muskets, bow & arrow, ... 
    Assumes all ranged weapons are disabled when they parry successfully.
    """
    logger = logging.getLogger('engine.RangedWeapon')
    operable = True
    
    def fire(self, target, obscured, distanceToTarget):
        self.logger.debug("Firing weapon on %s at %s." % (self.owner, target))
        if not self.operable: return False
        hitTarget, damage = self.getDamage(obscured, distanceToTarget)
        if hitTarget and damage > 0:
            self.logger.debug("fire: Hitting target for %s damage" % damage)
            target.takeDamage(damage)
        elif not hitTarget and damage > 0:
            self.logger.debug("fire: Hitting owner for %s damage" % damage)
            self.owner.takeDamage(damage)
    
    def getDamage(self, obscured, distanceToTarget):
        if not self.operable: return False, 0
        
        toHit = target.toHitRoll
        dieRoll = self.toHitDice.rollSum()
        if hasattr(self.owner, 'applySkills'):
            self.owner.applySkills('combat.attackroll', dieRoll)
        
        self.logger.debug('getDamage: To-hit roll (raw): %s' % dieRoll)
        
        if obscured:
            dieRoll -= self.obscuredPenalty
            self.logger.debug('  With obscured target penalty: %s' % dieRoll)
        
        if distanceToTarget > self.range:
            dieRoll -= int( (distanceToTarget - self.range) * self.accuracyLostPerInch )
        
        if dieRoll <= self.criticalMissMax:
            hitTarget, damage = False, self.criticalMissDamage
            self.logger.debug('  Critical miss.')
        elif toHit <= dieRoll < self.criticalHit:
            hitTarget, damage = True, self.normalHitDamage
            self.logger.debug('  Normal hit.')
        elif dieRoll >= self.criticalHit:
            hitTarget, damage = True, self.criticalHitDamage
            self.logger.debug('  Critical hit.')
        else:
            hitTarget, damage = True, 0
            self.logger.debug('  Normal miss.')
        
        if hitTarget and hasattr(self.owner, 'applySkills'):
            self.owner.applySkills('combat.attackdamage', damage)
        
        return hitTarget, damage
    
    def parry(self, attacker):
        parryRoll = self.toParryDice.roll()
        if hasattr(self.owner, 'applySkills'):
            parryRoll = self.owner.applySkills('combat.parryroll', parryRoll)
        if parryRoll > self.parryOn:
            #We manage to parry
            self.operable = False
            return True
        else:
            return False

class MeleeWeapon(Weapon):
    """
    For contact weapons like cutlasses, clubs, oars, shovels, ...
    """
    def fire(self, target, obscured, distanceToTarget):
        self.logger.debug("Firing weapon %s at %s" % (self, target))
        if distanceToTarget > self.range:
            self.logger.debug("  Too far to target. Failing.")
            return
        
        toHit = target.toHitRoll
        tohitRoll = self.toHitDice.rollSum()
        
        if hasattr(self.owner, 'applySkills'):
            self.owner.applySkills('combat.attackroll', tohitRoll)
        if obscured:
            tohitRoll -= self.obscuredPenalty
             
        if tohitRoll < self.criticalMiss:
            self.owner.takeDamage(self.criticalMissDamage)
            return
        elif self.toHit <= tohitRoll < self.criticalHit:
            damage = random.randint(self.damageMin, self.damageMax)
        elif self.criticalHit <= tohitRoll:
            damage = 2*random.randint(self.damageMin, self.damageMax)
        if hasattr(self.owner, 'applySkills'):
            self.owner.applySkills('combat.attackdamage', damage)
        if not target.parry(self.owner, self, damage):
            target.takeDamage(damage)
        
    def parry(self, attacker):
        parryRoll = self.toParryDice.roll()
        if hasattr(self.owner, 'applySkills'):
            parryRoll = self.owner.applySkills('combat.parryroll', parryRoll)
        return (parryRoll  >  self.parryOn)
